#include "accounthandler.h"
#include "storage.h"
#include "character.h"
#include "accountclient.h"
#include "common/configuration.h"
#include "common/manaserv_protocol.h"
#include "common/defines.h"
#include "net/connectionhandler.h"
#include "net/messagein.h"
#include "net/messageout.h"
#include "net/netcomputer.h"
#include "utils/functors.h"
#include "utils/logger.h"
#include "utils/point.h"
#include "utils/string.h"
#include "utils/xml.h"
#include "utils/tokencollector.h"
using namespace ManaServ;

class AccountHandler : public ConnectionHandler
{
public:
    AccountHandler(const std::string &attributesFile);
    TokenCollector<AccountHandler, AccountClient *, int> mTokenCollector;

    void tokenMatched(AccountClient *client, int accountID);
    void deletePendingClient(AccountClient *client);
    void deletePendingConnect(int) {}
protected:
    void processMessage(NetComputer *client, MessageIn &message);
    NetComputer *computerConnected(ENetPeer *peer);
    void computerDisconnected(NetComputer *comp);
private:
    std::vector<int> mModifiableAttributes;
    AttributeMap mDefaultAttributes;
    int mStartingPoints;   /**< Character's starting points. */
    int mAttributeMinimum; /**< Minimum value for customized attributes. */
    int mAttributeMaximum; /**< Maximum value for customized attributes. */
};

static AccountHandler *accountHandler;

AccountHandler::AccountHandler(const std::string &attributesFile):
    mTokenCollector(this)
{
    XML::Document doc(attributesFile);
    xmlNodePtr node = doc.rootNode();

    if (!node || !xmlStrEqual(node->name, BAD_CAST "attributes"))
    {
        LOG_FATAL("Account handler: " << attributesFile << ": "
                  << " is not a valid database file!");
        exit(EXIT_XML_BAD_PARAMETER);
    }

    for_each_xml_child_node(attributenode, node)
    {
        if (xmlStrEqual(attributenode->name, BAD_CAST "attribute"))
        {
            int id = XML::getProperty(attributenode, "id", 0);
            if (!id)
            {
                LOG_WARN("Account handler: " << attributesFile << ": "
                         << "An invalid attribute id value (0) has been found "
                         << "and will be ignored.");
                continue;
            }

            if (XML::getBoolProperty(attributenode, "modifiable", false))
                mModifiableAttributes.push_back(id);

            // Store as string initially to check
            // that the property is defined.
            std::string defStr = XML::getProperty(attributenode, "default",
                                                  std::string());
            if (!defStr.empty())
            {
                const double val = string_to<double>()(defStr);
                mDefaultAttributes.insert(std::make_pair(id, val));
            }
        }
        else if (xmlStrEqual(attributenode->name, BAD_CAST "points"))
        {
            mStartingPoints = XML::getProperty(attributenode, "start", 0);
            mAttributeMinimum = XML::getProperty(attributenode, "minimum", 0);
            mAttributeMaximum = XML::getProperty(attributenode, "maximum", 0);

            // Stops if not all the values are given.
            if (!mStartingPoints || !mAttributeMinimum || !mAttributeMaximum)
            {
                LOG_FATAL("Account handler: " << attributesFile << ": "
                          << " The characters starting points "
                          << "are incomplete or not set!");
                exit(EXIT_XML_BAD_PARAMETER);
            }
        }
    } // End for each XML nodes

    if (mModifiableAttributes.empty())
    {
        LOG_FATAL("Account handler: " << attributesFile << ": "
                  << "No modifiable attributes found!");
        exit(EXIT_XML_BAD_PARAMETER);
    }

    int attributeCount = (int) mModifiableAttributes.size();
    if (attributeCount * mAttributeMaximum < mStartingPoints ||
        attributeCount * mAttributeMinimum > mStartingPoints)
    {
        LOG_FATAL("Account handler: " << attributesFile << ": "
                  << "Character's point values make "
                  << "the character's creation impossible!");
        exit(EXIT_XML_BAD_PARAMETER);
    }

    LOG_DEBUG("Character start points: " << mStartingPoints << " (Min: "
              << mAttributeMinimum << ", Max: " << mAttributeMaximum << ")");
}

bool AccountClientHandler::initialize(const std::string &attributesFile, int port,
                                      const std::string &host)
{
    accountHandler = new AccountHandler(attributesFile);
    LOG_INFO("Account handler started:");

    return accountHandler->startListen(port, host);
}

void AccountClientHandler::deinitialize()
{
    accountHandler->stopListen();
    delete accountHandler;
    accountHandler = 0;
}

void AccountClientHandler::process()
{
    accountHandler->process(50);
}

void AccountClientHandler::prepareReconnect(const std::string &token, int id)
{
    //accountHandler->mTokenCollector.addPendingConnect(token, id);
}

NetComputer *AccountHandler::computerConnected(ENetPeer *peer)
{
    return new AccountClient(peer);
}

void AccountHandler::computerDisconnected(NetComputer *comp)
{
    AccountClient *client = static_cast<AccountClient *>(comp);
//
//    if (client->status == CLIENT_QUEUED)
//        // Delete it from the pendingClient list
//        mTokenCollector.deletePendingClient(client);

    delete client; // ~AccountClient unsets the account
}

void AccountHandler::processMessage(NetComputer *comp, MessageIn &message)
{
    AccountClient &client = *static_cast< AccountClient * >(comp);

    switch (message.getId())
    {
        case PAMSG_LOGIN:
            LOG_DEBUG("Received msg ... PAMSG_LOGIN");
            //handleLoginMessage(client, message);
            break;

        case PAMSG_LOGOUT:
            LOG_DEBUG("Received msg ... PAMSG_LOGOUT");
            //handleLogoutMessage(client);
            break;

        case PAMSG_RECONNECT:
            LOG_DEBUG("Received msg ... PAMSG_RECONNECT");
            //handleReconnectMessage(client, message);
            break;

        case PAMSG_REGISTER:
            LOG_DEBUG("Received msg ... PAMSG_REGISTER");
            //handleRegisterMessage(client, message);
            break;

        case PAMSG_UNREGISTER:
            LOG_DEBUG("Received msg ... PAMSG_UNREGISTER");
            //handleUnregisterMessage(client, message);
            break;

        case PAMSG_REQUEST_REGISTER_INFO :
            LOG_DEBUG("Received msg ... REQUEST_REGISTER_INFO");
            //handleRequestRegisterInfoMessage(client, message);
            break;

        case PAMSG_EMAIL_CHANGE:
            LOG_DEBUG("Received msg ... PAMSG_EMAIL_CHANGE");
            //handleEmailChangeMessage(client, message);
            break;

        case PAMSG_PASSWORD_CHANGE:
            LOG_DEBUG("Received msg ... PAMSG_PASSWORD_CHANGE");
            //handlePasswordChangeMessage(client, message);
            break;

        case PAMSG_CHAR_CREATE:
            LOG_DEBUG("Received msg ... PAMSG_CHAR_CREATE");
            //handleCharacterCreateMessage(client, message);
            break;

        case PAMSG_CHAR_SELECT:
            LOG_DEBUG("Received msg ... PAMSG_CHAR_SELECT");
            //handleCharacterSelectMessage(client, message);
            break;

        case PAMSG_CHAR_DELETE:
            LOG_DEBUG("Received msg ... PAMSG_CHAR_DELETE");
            //handleCharacterDeleteMessage(client, message);
            break;

        default:
            LOG_WARN("AccountHandler::processMessage, Invalid message type "
                     << message.getId());
            MessageOut result(XXMSG_INVALID);
            client.send(result);
            break;
    }
}

void AccountHandler::tokenMatched(AccountClient *client, int accountID)
{
    /*
    MessageOut reply(APMSG_RECONNECT_RESPONSE);

    // Associate account with connection.
    Account *acc = storage->getAccount(accountID);
    client->setAccount(acc);
    client->status = CLIENT_CONNECTED;

    reply.writeInt8(ERRMSG_OK);
    client->send(reply);

    // Return information about available characters
    Characters &chars = acc->getCharacters();

    // Send characters list
    for (Characters::const_iterator i = chars.begin(), i_end = chars.end();
         i != i_end; ++i)
        sendCharacterData(*client, *(*i).second);
    */
}

void AccountHandler::deletePendingClient(AccountClient *client)
{
    MessageOut msg(APMSG_RECONNECT_RESPONSE);
    msg.writeInt8(ERRMSG_TIME_OUT);
    client->disconnect(msg);
    // The client will be deleted when the disconnect event is processed
}

